<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ current_path | default(value="文件列表") }}</title>
    <script src="/assets/tailwind.js"></script>
    <script src="/assets/spark-md5.min.js"></script>
    <link href="/assets/fontawesome-free-6.7.2-web/css/all.min.css" rel="stylesheet">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#165DFF',
                        secondary: '#4080FF',
                        success: '#00B42A',
                        warning: '#FF7D00',
                        danger: '#F53F3F',
                        dark: '#1D2129',
                        light: '#F2F3F5',
                    },
                    fontFamily: {
                        inter: ['Inter', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .bg-gradient-blue {
                background: linear-gradient(135deg, #165DFF 0%, #4080FF 100%);
            }
            .shadow-card {
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            }
            .transition-all-300 {
                transition: all 0.3s ease;
            }
            .tab-active {
                @apply border-primary text-primary;
            }
            .table-row-hover {
                @apply hover:bg-gray-50 transition-all-300;
            }
            .drop-area-active {
                @apply border-primary bg-primary/5;
            }
            .file-tag {
                @apply bg-gray-100 px-3 py-1.5 rounded-full text-sm text-gray-700 transition-all-300 hover:bg-gray-200;
            }
            .btn-disabled {
                @apply bg-gray-300 text-gray-500 cursor-not-allowed hover:bg-gray-300 shadow-none;
            }
            .toast-container {
                @apply fixed bottom-6 right-6 z-50 flex flex-col gap-3;
            }
            .toast-message {
                @apply bg-success text-white px-4 py-3 rounded-lg shadow-lg flex items-center max-w-md w-full transform transition-all duration-300;
            }
        }
    </style>
</head>
<body class="bg-gray-50 font-inter text-dark min-h-screen flex flex-col">
    <!-- 顶部导航栏 -->
    <header class="bg-white shadow-sm sticky top-0 z-50">
        <div class="container mx-auto px-4 py-3 flex items-center justify-between">
            <div class="flex items-center space-x-2">
                <i class="fa fa-folder text-primary text-xl"></i>
                <h1 class="text-xl font-bold text-primary">{{ current_path | default(value="文件列表") }}</h1>
            </div>
            <nav class="hidden md:flex items-center space-x-6">
                <a href="#" class="text-primary font-medium flex items-center">
                    <i class="fa fa-tachometer mr-2"></i> 仪表盘
                </a>
                <a href="/file-browser" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                    <i class="fa fa-folder-open mr-2"></i> 根目录设置
                </a>
                <a href="/ai-search.html" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                    <i class="fa fa-search mr-2"></i> 人工智能检索
                </a>
                <a href="showlovewall.html" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                    <i class="fa fa-heartbeat mr-2"></i> 表白墙
                </a>
                <a href="/user-pwd-settings" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                    <i class="fa fa-user-circle mr-2"></i> 管理员设置
                </a>
            </nav>
            <div class="flex items-center space-x-4">
                <a href="/logs" class="text-gray-600 hover:text-primary transition-all duration-300 inline-block">
                    <i class="fa fa-bell text-xl"></i>
                </a>
                <div class="relative">
                    <button class="flex items-center space-x-2 focus:outline-none" id="adminDropdownBtn">
                        <img src="https://picsum.photos/200/200?random=1" alt="管理员头像" class="w-8 h-8 rounded-full object-cover border-2 border-primary">
                        <span class="hidden md:inline-block font-medium">admin</span>
                        <i class="fa fa-angle-down text-gray-500"></i>
                    </button>
                    <ul id="adminDropdownMenu" class="absolute right-0 mt-2 w-48 bg-white shadow-lg rounded-lg py-1 hidden">
                        <li>
                            <a href="/user-pwd-settings" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
                                <i class="fa fa-key mr-2"></i> 修改密码
                            </a>
                        </li>
                        <li>
                            <a href="/logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary hover:text-white transition-colors">
                                <i class="fa fa-sign-out-alt mr-2"></i> 登出
                            </a>
                        </li>
                    </ul>
                </div>
                <button class="md:hidden text-gray-600 hover:text-primary transition-all-300">
                    <i class="fa fa-bars text-xl"></i>
                </button>
            </div>
        </div>
    </header>

    <!-- 主要内容区 -->
    <main class="flex-grow container mx-auto px-4 py-8">
        <div class="max-w-5xl mx-auto">
            <div class="mb-8">
                <h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-dark mb-2">文件管理中心</h2>
                <p class="text-gray-500">管理文件和目录信息</p>
            </div>

            <!-- 面包屑导航 -->
            <div class="mb-6">
                <div class="flex items-center space-x-2 text-sm text-gray-500">
                    <a href="/" class="text-primary hover:underline">根目录</a>
                    {% for item in breadcrumbs %}
                        <span>/</span>
                        <a href="/{{ item.url }}" class="text-primary hover:underline">{{ item.name }}</a>
                    {% endfor %}
                </div>
            </div>

            <!-- 返回上级目录 -->
            {% if parent_path != current_path %}
            <div class="mb-6">
                <a href="{{ parent_path }}" class="text-primary hover:underline flex items-center">
                    <i class="fa fa-arrow-left mr-2"></i> 返回上级目录
                </a>
            </div>
            {% endif %}

            <!-- 文件上传（大文件高速上传版） -->
            <div class="bg-white rounded-xl shadow-card p-5 md:p-6 mb-6 md:mb-8 transition-all-300 hover:shadow-hover">
                <h3 class="text-lg md:text-xl font-bold text-neutral-600 mb-4">大文件高速上传</h3>
                
                <!-- 拖放区域 -->
                <div id="dropArea" class="mb-4 border-2 border-dashed border-neutral-300 rounded-lg p-8 text-center transition-all-300 hover:border-primary/50">
                    <i class="fa fa-cloud-upload text-4xl text-neutral-400 mb-4 transition-all-300 hover:text-primary"></i>
                    <h4 class="text-lg font-medium text-neutral-700 mb-2">拖放文件到此处上传</h4>
                    <p class="text-neutral-500 mb-4">支持大文件分片上传、断点续传（单文件最大支持10GB）</p>
                    <label for="fileInput" class="inline-block px-5 py-2.5 bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-all-300 cursor-pointer">
                        <i class="fa fa-folder-open mr-2"></i> 选择文件
                    </label>
                </div>

                <!-- 隐藏的文件输入 -->
                <div class="mb-4 hidden">
                    <input 
                        type="file" 
                        id="fileInput" 
                        name="file" 
                        multiple 
                        class="w-full px-4 py-2.5 rounded-lg border border-neutral-300 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all-300 bg-white"
                        onchange="handleFileSelect(this.files)"
                    >
                </div>

                <!-- 错误提示 -->
                <div id="dropError" class="mb-4 text-danger text-sm hidden">
                    <i class="fa fa-exclamation-circle mr-1"></i>
                    <span id="dropErrorText">不支持的文件类型，请重新选择</span>
                </div>

                <!-- 已选择文件列表 -->
                <div id="selectedFilesContainer" class="mb-4 flex flex-col gap-3 hidden">
                    <!-- 动态生成文件项 -->
                </div>

                <!-- 操作按钮 -->
                <div class="flex flex-wrap items-center gap-3">
                    <button 
                        type="button" 
                        id="uploadBtn" 
                        class="px-5 py-2.5 bg-primary text-white rounded-lg hover:bg-primary/90 transition-all-300 flex items-center shadow-sm hover:shadow"
                        onclick="startBatchUpload()"
                    >
                        <i class="fa fa-upload mr-2"></i> 开始上传
                    </button>
                    <button 
                        type="button" 
                        id="pauseBtn" 
                        class="px-4 py-2.5 bg-yellow-50 text-yellow-600 rounded-lg hover:bg-yellow-100 transition-all-300 hidden"
                        onclick="pauseBatchUpload()"
                    >
                        <i class="fa fa-pause mr-1"></i> 暂停上传
                    </button>
                    <button 
                        type="button" 
                        id="clearFilesBtn" 
                        class="px-4 py-2.5 bg-white border border-neutral-300 text-neutral-500 rounded-lg hover:bg-neutral-100 transition-all-300"
                        onclick="clearSelectedFiles(true)"
                    >
                        <i class="fa fa-times mr-1"></i> 清空选择
                    </button>
                </div>

                <!-- 整体进度与状态 -->
                <div id="progressContainer" class="mt-4 hidden">
                    <div class="w-full h-2 bg-neutral-200 rounded-full overflow-hidden">
                        <div id="totalProgressBar" class="h-full bg-primary transition-all duration-300" style="width: 0%"></div>
                    </div>
                    <div class="mt-2 flex justify-between text-sm">
                        <div id="totalProgressText" class="text-neutral-400">准备上传...</div>
                        <div id="uploadSpeed" class="text-neutral-400">速度：--</div>
                    </div>
                </div>

                <!-- 上传结果提示 -->
                <p class="mt-4 text-{{ upload_result_color }}-500">{{ upload_result }}</p>
            </div>

            <!-- 手机访问本网站 -->
            <div class="bg-white rounded-xl shadow-card p-6 mb-8">
                <h3 class="text-xl font-bold mb-4">手机访问本网站</h3>
                <div class="qrcode-container">
                    <img src="{{ qrcode_base64 }}" alt="访问网站的QR码" class="max-w-full h-auto border border-gray-300 rounded-md w-48 mx-auto" title="{{ qr_url }}">
                </div>
            </div>

            <!-- 目录列表 -->
            <div class="bg-white rounded-xl shadow-card p-6 mb-8">
                <h3 class="text-xl font-bold mb-4">目录</h3>
                <ul class="divide-y divide-gray-200">
                    {% for dir in directories %}
                    <li class="py-4 flex items-center">
                        <a href="/{{ dir.path }}" class="flex items-center space-x-2 text-gray-700 hover:text-primary transition-all-300">
                            <i class="fa fa-folder text-primary"></i>
                            <span>{{ dir.name }}</span>
                        </a>
                        <span class="ml-auto text-sm text-gray-500">{{ dir.modified }}</span>
                    </li>
                    {% endfor %}
                </ul>
            </div>

            <!-- 文件列表 -->
            <div class="bg-white rounded-xl shadow-card p-6 mb-8">
                <h3 class="text-xl font-bold mb-4">文件</h3>
                <ul class="divide-y divide-gray-200">
                    {% for file in files %}
                    <li class="py-4 flex items-center">
                        <a href="/download/{{ file.path }}" class="flex items-center space-x-2 text-gray-700 hover:text-primary transition-all-300">
                            <i class="fa fa-file text-gray-500"></i>
                            <span>{{ file.name }}</span>
                        </a>
                        <span class="ml-auto text-sm text-gray-500">{{ file.size | filesizeformat }}</span>
                        <span class="ml-4 text-sm text-gray-500">{{ file.modified }}</span>
                    </li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    </main>

    <!-- 页脚 -->
    <footer class="bg-white border-t border-gray-200 py-6">
        <div class="container mx-auto px-4">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-4 md:mb-0">
                    <p class="text-gray-500 text-sm">&copy; 2025 北京椰子树信息技术有限公司. 保留所有权利.</p>
                </div>
                <div class="flex space-x-6">
                    <a href="/file-browser" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                        <i class="fa fa-folder-open mr-2"></i> 根目录设置
                    </a>
                    <a href="/ai-search.html" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                        <i class="fa fa-search mr-2"></i> 人工智能检索
                    </a>
                    <a href="showlovewall.html" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                        <i class="fa fa-heartbeat mr-2"></i> 表白墙
                    </a>
                    <a href="/user-pwd-settings" class="text-gray-600 hover:text-primary transition-all-300 flex items-center">
                        <i class="fa fa-user-circle mr-2"></i> 管理员设置
                    </a>
                    <a href="http://www.coconet.cn" class="text-gray-500 hover:text-primary transition-all-300">
                        <i class="fa fa-question-circle mr-1"></i> 帮助中心
                    </a>
                    <a href="http://www.coconet.cn" class="text-gray-500 hover:text-primary transition-all-300">
                        <i class="fa fa-file-text-o mr-1"></i> 隐私政策
                    </a>
                    <a href="mailto:goodput3@qq.com" class="text-gray-500 hover:text-primary transition-all-300">
                        <i class="fa fa-phone mr-1"></i> 联系我们
                    </a>
                </div>
            </div>
        </div>
    </footer>

    <!-- 提示框容器 -->
    <div id="toastContainer" class="toast-container"></div>

    <script>
        // 管理员下拉菜单逻辑
        const dropdownBtn = document.getElementById('adminDropdownBtn');
        const dropdownMenu = document.getElementById('adminDropdownMenu');
        let toastQueue = [];
        let busyCounter = 0;        // 正在忙的任务数
        const fileInput  = document.getElementById('fileInput');
        const dropArea   = document.getElementById('dropArea');
        const uploadBtn  = document.getElementById('uploadBtn');

        /* ---------- 工具：更新按钮/区域可用状态 ---------- */
        function updateFileSelectAvailable() {
            const busy = busyCounter > 0;
            fileInput.disabled = busy;
            dropArea.classList.toggle('pointer-events-none', busy);   // Tailwind 禁用鼠标
            dropArea.classList.toggle('opacity-50', busy);            // 视觉灰化
            uploadBtn.disabled = busy;                                // 顺便把“开始上传”也禁掉
        }

        /* ---------- 包装：任何异步任务都用它包一层 ---------- */
        async function runBusy(task) {
            busyCounter++;
            updateFileSelectAvailable();
            try { await task(); }
            finally {
                busyCounter--;
                updateFileSelectAvailable();
            }
        }
        function showSuccessToastOnce() {
            if (!toastQueue.length) return;
            const text = toastQueue.length === 1
                ? `文件 <strong>${toastQueue[0]}</strong> 上传成功`
                : `${toastQueue.length} 个文件上传成功`;
            const container = document.getElementById('toastContainer') || createToastContainer();
            const toast = document.createElement('div');
            toast.className = 'toast-message';
            toast.innerHTML = `<i class="fa fa-check-circle mr-2"></i><span>${text}</span>`;
            container.appendChild(toast);
            setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 3000);
            toastQueue = [];
        }

        function stopSpeedCalculation() {
            if (speedTimer) { clearInterval(speedTimer); speedTimer = null; }
        }      
        dropdownBtn.addEventListener('click', () => {
            dropdownMenu.classList.toggle('hidden');
            dropdownBtn.querySelector('.fa-angle-down').classList.toggle('rotate-180');
        });
      
        window.addEventListener('click', (event) => {
            if (!event.target.closest('#adminDropdownBtn, #adminDropdownMenu')) {
                dropdownMenu.classList.add('hidden');
                dropdownBtn.querySelector('.fa-angle-down').classList.remove('rotate-180');
            }
        });
        /* ---------- 6. 文件选择/拖放入口加锁 ---------- */
        /* 防止用户强行点击 */
        fileInput.addEventListener('click', e => {
        if (busyCounter > 0) {
            e.preventDefault();
            showDropError('正在上传中，请稍后再选择文件');
        }
        }, true);

        dropArea.addEventListener('drop', e => {
        if (busyCounter > 0) {
            preventDefaults(e);
            showDropError('正在上传中，无法接收新文件');
        }
        });
        // 上传配置参数
        const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB 分片大小
        const MAX_PARALLEL = 4; // 最大并行上传数
        const UPLOAD_CHUNK_API = "/upload-chunk"; // 分片上传接口
        const MERGE_API = "/merge-chunks"; // 合并接口
        const CHECK_CHUNKS_API = "/check-chunks"; // 断点续传查询接口

        // 全局状态（关键改进：确保所有状态可被正确重置）
        let selectedFiles = []; // 已选择的文件列表（含分片信息）
        let isUploading = false; // 是否正在上传
        let isPreparingFiles = false; // 是否正在准备文件（计算哈希/查分片）
        let speedTimer = null; // 速度计算定时器
        let activeUploads = 0; // 当前活跃上传数（用于判断是否全部完成）

        // 初始化拖拽事件
        function initDragAndDrop() {
            const dropArea = document.getElementById("dropArea");
            
            // 阻止浏览器默认拖放行为
            ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
                dropArea.addEventListener(eventName, preventDefaults, false);
                document.body.addEventListener(eventName, preventDefaults, false);
            });

            // 拖放区域视觉反馈
            ['dragenter', 'dragover'].forEach(eventName => {
                dropArea.addEventListener(eventName, () => {
                    dropArea.classList.add('drop-area-active');
                }, false);
            });

            ['dragleave', 'drop'].forEach(eventName => {
                dropArea.addEventListener(eventName, () => {
                    dropArea.classList.remove('drop-area-active');
                }, false);
            });

            // 处理文件放下事件
            dropArea.addEventListener('drop', handleDrop, false);
        }

        // 阻止默认行为
        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }

        // 处理拖放文件
        function handleDrop(e) {
            e.preventDefault();
            e.stopPropagation();

            const dt = e.dataTransfer;
            let droppedFiles = [];

            // Firefox 兼容：优先从 items 中提取文件
            if (dt.items && dt.items.length > 0) {
                for (let i = 0; i < dt.items.length; i++) {
                    const item = dt.items[i];
                    if (item.kind === 'file') {
                        const file = item.getAsFile();
                        if (file) droppedFiles.push(file);
                    }
                }
                dt.items.clear();
            } else {
                droppedFiles = Array.from(dt.files);
            }

            // 检测是否有有效文件
            if (droppedFiles.length === 0) {
                showDropError('未检测到有效文件，请重新拖放');
                return;
            }

            // 过滤文件夹
            for (const file of droppedFiles) {
                if (file.name.endsWith('/')) {
                    showDropError('不支持上传文件夹，请选择文件');
                    return;
                }
            }

            handleFileSelect(droppedFiles);
        }

        // 显示拖放错误提示
        function showDropError(message) {
            const dropError = document.getElementById("dropError");
            const dropErrorText = document.getElementById("dropErrorText");
            dropErrorText.textContent = message;
            dropError.classList.remove('hidden');
            setTimeout(() => {
                dropError.classList.add('hidden');
            }, 3000);
        }

        // 显示上传成功提示
        function showSuccessToast(filename) {
            let toastContainer = document.getElementById('toastContainer');
            if (!toastContainer) {
                toastContainer = document.createElement('div');
                toastContainer.id = 'toastContainer';
                toastContainer.className = 'toast-container';
                document.body.appendChild(toastContainer);
            }

            const toast = document.createElement('div');
            toast.className = 'toast-message';
            toast.innerHTML = `
                <i class="fa fa-check-circle mr-2 text-xl"></i>
                <span>文件 <strong>${filename}</strong> 上传成功</span>
            `;

            toastContainer.appendChild(toast);
            
            // 3秒后自动移除
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => {
                    toast.remove();
                }, 300);
            }, 3000);
        }

        // 处理文件选择
        function handleFileSelect(files) {
            if (files.length === 0) return;
            if(busyCounter > 0) {
                showSuccessToast("当前有文件正在处理中，请稍后再试");
                return;
            }   
            selectedFiles = []; // 清空原有列表
            isPreparingFiles = true; // 开启文件准备锁
            busyCounter++;
            updateFileSelectAvailable();
            // 更新UI状态
            const selectedContainer = document.getElementById("selectedFilesContainer");
            const uploadBtn = document.getElementById("uploadBtn");
            const progressContainer = document.getElementById("progressContainer");
            
            selectedContainer.classList.remove("hidden");
            document.getElementById("clearFilesBtn").classList.remove("hidden");
            progressContainer.classList.remove("hidden");
            uploadBtn.disabled = true;
            uploadBtn.classList.add("btn-disabled");
            uploadBtn.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i> 准备文件中...';
            document.getElementById("totalProgressText").textContent = "正在计算文件信息，请稍候...";

            // 处理每个文件（计算哈希+查分片）
            const filePromises = Array.from(files).map(async (file) => {
                const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
                const fileHash = await calculateFileHash(file);
                const uploadedChunks = await checkUploadedChunks(fileHash, totalChunks);

                const fileInfo = {
                    file: file,
                    fileName: file.name,
                    fileSize: file.size,
                    fileHash: fileHash,
                    totalChunks: totalChunks,
                    uploadedChunks: uploadedChunks,
                    completedChunks: uploadedChunks.length,
                    progress: (uploadedChunks.length / totalChunks) * 100,
                    status: "ready" // ready / uploading / paused / completed / failed
                };
                selectedFiles.push(fileInfo);
                renderFileList();
            });

            // 所有文件准备完成后，启用上传按钮
            Promise.all(filePromises).then(() => {
                isPreparingFiles = false;
                uploadBtn.disabled = false;
                uploadBtn.classList.remove("btn-disabled");
                uploadBtn.innerHTML = '<i class="fa fa-upload mr-2"></i> 开始上传';
                document.getElementById("totalProgressText").textContent = "准备上传...";
                updateTotalProgress();
            }).catch((err) => {
                // 错误处理时强制重置状态（关键改进）
                resetUploadStates();
                showDropError("文件准备失败，请重新选择");
                console.error("文件准备错误：", err);
            });
            //busyCounter--;
            //updateFileSelectAvailable();
        }

        // 计算文件哈希
        async function calculateFileHash(file) {
            return new Promise((resolve) => {
                const spark = new SparkMD5.ArrayBuffer();
                const fileReader = new FileReader();
                const chunkSize = 2 * 1024 * 1024; // 2MB 分片读取
                let currentOffset = 0;

                function loadNextChunk() {
                    const start = currentOffset;
                    const end = Math.min(start + chunkSize, file.size);
                    fileReader.readAsArrayBuffer(file.slice(start, end));
                }

                fileReader.onload = (e) => {
                    spark.append(e.target.result);
                    currentOffset += chunkSize;
                    if (currentOffset >= file.size) {
                        resolve(spark.end());
                    } else {
                        loadNextChunk();
                    }
                };

                loadNextChunk();
            });
        }

        // 查询已上传的分片
        async function checkUploadedChunks(fileHash, totalChunks) {
            try {
                const response = await fetch(`${CHECK_CHUNKS_API}?file_hash=${fileHash}`, {
                    method: "GET",
                    credentials: "include"
                });
                if (response.ok) {
                    const data = await response.json();
                    return data.uploaded_chunks || [];
                }
                return [];
            } catch (e) {
                console.error("查询已上传分片失败：", e);
                return [];
            }
        }

        // 切割分片
        function getChunk(file, chunkIndex) {
            const start = chunkIndex * CHUNK_SIZE;
            const end = Math.min(start + CHUNK_SIZE, file.size);
            return file.slice(start, end);
        }

        // 批量开始上传
        async function startBatchUpload() {
            // 状态校验（防止重复点击）
            console.log("start batch upload, ", isPreparingFiles, " len: ", selectedFiles.length, " is uploading: ", isUploading);
            if (isPreparingFiles || selectedFiles.length === 0 || isUploading) return;
            
            // 初始化上传状态
            isUploading = true;
            activeUploads = selectedFiles.length; // 记录活跃上传数
            document.getElementById("uploadBtn").classList.add("hidden");
            document.getElementById("pauseBtn").classList.remove("hidden");
            document.getElementById("totalProgressText").textContent = "正在上传...";

            // 启动速度计算
            startSpeedCalculation();

            // 对每个文件启动上传
            selectedFiles.forEach(fileInfo => {
                if (fileInfo.status !== "uploading") {
                    console.log("uploading file chunks...")
                    fileInfo.status = "uploading";
                    uploadFileChunks(fileInfo);
                }
            });
        }

        // 上传单个文件的所有分片
        async function uploadFileChunks(fileInfo) {
            const { file, fileHash, totalChunks, uploadedChunks } = fileInfo;
            const chunkQueue = [];
            console.log("upload file chunks: ", totalChunks, " uploadedChunks: ", uploadedChunks);
            // 生成未上传的分片队列
            for (let i = 0; i < totalChunks; i++) {
                if (!uploadedChunks.includes(i)) {
                    chunkQueue.push(i);
                }
            }
            // 标记“尚未开始 merge”
            let merging = false;
            // 并行上传控制
            // 并行上传控制
            const parallelUpload = async () => {
                // 还有分片就继续传
                if (chunkQueue.length) {
                    const idx = chunkQueue.shift();
                    try {
                        await uploadSingleChunk(fileInfo, idx);
                        fileInfo.completedChunks++;
                        fileInfo.progress = (fileInfo.completedChunks / totalChunks) * 100;
                        fileInfo.uploadedChunks.push(idx);
                        updateFileProgress(fileInfo);
                        updateTotalProgress();
                    } catch (e) {
                        console.error(`分片 ${idx} 失败`, e);
                        chunkQueue.push(idx);          // 失败重试
                        fileInfo.status = 'failed';
                        updateFileProgress(fileInfo);
                    }
                    // 递归继续
                    return parallelUpload();
                }

                // -------------- 关键：只 merge 一次 --------------
                if (!merging && fileInfo.completedChunks === totalChunks) {
                    merging = true;                    // 上锁
                    fileInfo.status = 'merging';
                    try {
                        await mergeFileChunks(fileInfo);
                        fileInfo.status = 'completed';
                        updateFileProgress(fileInfo);

                        // 收集待提示文件名
                        toastQueue.push(fileInfo.fileName);

                        // 递减活跃文件数
                        activeUploads--;
                        if (activeUploads <= 0) {
                            stopSpeedCalculation();
                            showSuccessToastOnce();    // 统一弹 1 次
                            setTimeout(resetUploadStates, 1000);
                        }
                    } catch (e) {
                        console.error('merge 失败', e);
                        fileInfo.status = 'failed';
                        updateFileProgress(fileInfo);
                        activeUploads--;
                        if (activeUploads <= 0) resetUploadStates();
                    }
                }
            };

            // 启动并行任务
            for (let i = 0; i < Math.min(MAX_PARALLEL, chunkQueue.length); i++) {
                parallelUpload();
            }
        }

        // 上传单个分片
        async function uploadSingleChunk(fileInfo, chunkIndex) {
            return runBusy(async () => {
                const { file, fileHash, totalChunks } = fileInfo;
                const chunk = getChunk(file, chunkIndex);

                const meta = JSON.stringify({
                    file_name: file.name,
                    file_hash: fileHash,
                    chunk_index: chunkIndex,
                    total_chunks: totalChunks
                });

                const formData = new FormData();
                formData.append("meta", meta);
                formData.append("chunk_data", chunk, `chunk_${chunkIndex}`);

                const response = await fetch(UPLOAD_CHUNK_API, {
                    method: "POST",
                    body: formData,
                    credentials: "include"
                });

                if (!response.ok) {
                    const errorText = await response.text();
                    throw new Error(`HTTP 错误：${response.status}，${errorText}`);
                }
            });
        }

        // 合并分片
        async function mergeFileChunks(fileInfo) {
            return runBusy(async () => {
                const { fileName, fileHash, totalChunks } = fileInfo;
                try {
                    const response = await fetch(MERGE_API, {
                        method: "POST",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({
                            file_name: fileName,
                            file_hash: fileHash,
                            total_chunks: totalChunks
                        }),
                        credentials: "include"
                    });

                    if (!response.ok) {
                        const errorText = await response.text();
                        throw new Error(`合并失败：${errorText}`);
                    }
                } catch (e) {
                    console.error("合并分片失败：", e);
                    throw e; // 抛出错误让上层处理
                }
            });
        }

        // 渲染文件列表
        function renderFileList() {
            const container = document.getElementById("selectedFilesContainer");
            container.innerHTML = "";

            selectedFiles.forEach((fileInfo) => {
                const fileSizeStr = formatFileSize(fileInfo.fileSize);
                const fileItem = document.createElement("div");
                fileItem.className = "file-item p-3 border border-neutral-200 rounded-lg";
                fileItem.innerHTML = `
                    <div class="flex justify-between items-center mb-2">
                        <span class="font-medium text-neutral-700">${fileInfo.fileName}</span>
                        <span class="text-sm text-neutral-500">${fileSizeStr} / ${fileInfo.totalChunks}个分片</span>
                    </div>
                    <div class="w-full h-2 bg-neutral-200 rounded-full mb-2">
                        <div class="file-progress h-full bg-primary" style="width: ${fileInfo.progress}%"></div>
                    </div>
                    <div class="flex justify-between text-xs text-neutral-500">
                        <span class="completed-chunks">已上传：${fileInfo.completedChunks}个分片</span>
                        <span class="file-status">状态：${getStatusText(fileInfo.status)}</span>
                    </div>
                `;
                container.appendChild(fileItem);
            });
        }

        // 更新单个文件进度
        function updateFileProgress(fileInfo) {
            const fileItems = document.querySelectorAll(".file-item");
            const index = selectedFiles.indexOf(fileInfo);
            if (index < 0 || index >= fileItems.length) return;

            const fileItem = fileItems[index];
            fileItem.querySelector(".file-progress").style.width = `${fileInfo.progress}%`;
            fileItem.querySelector(".completed-chunks").textContent = `已上传：${fileInfo.completedChunks}个分片`;
            fileItem.querySelector(".file-status").textContent = `状态：${getStatusText(fileInfo.status)}`;
        }

        // 更新整体进度
        function updateTotalProgress() {
            let totalSize = 0;
            let completedSize = 0;

            selectedFiles.forEach(fileInfo => {
                totalSize += fileInfo.fileSize;
                completedSize += (fileInfo.completedChunks / fileInfo.totalChunks) * fileInfo.fileSize;
            });

            const totalProgress = (completedSize / totalSize) * 100;
            document.getElementById("totalProgressBar").style.width = `${totalProgress.toFixed(1)}%`;
            document.getElementById("totalProgressText").textContent = `整体进度：${totalProgress.toFixed(1)}%`;
        }

        // 计算上传速度
        function startSpeedCalculation() {
            // 清除可能存在的旧定时器（关键改进）
            busyCounter++;
            updateFileSelectAvailable();
            if (speedTimer) clearInterval(speedTimer);
            
            let lastCompletedSize = 0;
            speedTimer = setInterval(() => {
                let currentCompletedSize = 0;
                selectedFiles.forEach(fileInfo => {
                    currentCompletedSize += (fileInfo.completedChunks / fileInfo.totalChunks) * fileInfo.fileSize;
                });

                const speed = (currentCompletedSize - lastCompletedSize) / 1024 / 1024; // MB/s
                document.getElementById("uploadSpeed").textContent = `速度：${speed.toFixed(1)}MB/s`;
                lastCompletedSize = currentCompletedSize;
            }, 1000);
            /* 停止时记得 decrement */
            const oldStop = stopSpeedCalculation;
            stopSpeedCalculation = () => {
                clearInterval(speedTimer); speedTimer = null;
                busyCounter--;
                updateFileSelectAvailable();
            };
        }

        // 格式化文件大小
        function formatFileSize(size) {
            if (size < 1024) return `${size}B`;
            if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}KB`;
            if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)}MB`;
            return `${(size / 1024 / 1024 / 1024).toFixed(1)}GB`;
        }

        // 获取状态文本
        function getStatusText(status) {
            const statusMap = {
                ready: "等待上传",
                uploading: "上传中",
                paused: "已暂停",
                completed: "上传完成",
                failed: "上传失败"
            };
            return statusMap[status] || "未知状态";
        }

        // 暂停上传
        function pauseBatchUpload() {
            isUploading = false;
            clearInterval(speedTimer);
            document.getElementById("pauseBtn").classList.add("hidden");
            document.getElementById("uploadBtn").classList.remove("hidden");
            document.getElementById("totalProgressText").textContent = "已暂停";
            selectedFiles.forEach(fileInfo => {
                if (fileInfo.status === "uploading") {
                    fileInfo.status = "paused";
                    updateFileProgress(fileInfo);
                }
            });
        }

        // 清空选择（新增force参数强制重置所有状态）
        function clearSelectedFiles(force = false) {
            selectedFiles = [];
            isPreparingFiles = false;
            isUploading = false; // 强制停止上传
            activeUploads = 0;
            clearInterval(speedTimer);
            
            // 恢复UI初始状态
            const uploadBtn = document.getElementById("uploadBtn");
            document.getElementById("selectedFilesContainer").classList.add("hidden");
            document.getElementById("clearFilesBtn").classList.add("hidden");
            document.getElementById("progressContainer").classList.add("hidden");
            document.getElementById("pauseBtn").classList.add("hidden");
            
            // 恢复上传按钮
            uploadBtn.classList.remove("hidden");
            uploadBtn.disabled = false;
            uploadBtn.classList.remove("btn-disabled");
            uploadBtn.innerHTML = '<i class="fa fa-upload mr-2"></i> 开始上传';
            busyCounter = 0;
            updateFileSelectAvailable();
        }

        // 重置上传状态（关键改进：统一处理上传完成/失败后的状态重置）
        function resetUploadStates() {
            isUploading = false;
            clearInterval(speedTimer);
            
            // 恢复按钮状态
            document.getElementById("pauseBtn").classList.add("hidden");
            document.getElementById("uploadBtn").classList.remove("hidden");
            document.getElementById("uploadBtn").disabled = false;
            document.getElementById("uploadBtn").classList.remove("btn-disabled");
            document.getElementById("uploadBtn").innerHTML = '<i class="fa fa-upload mr-2"></i> 开始上传';
            
            // 更新进度文本
            document.getElementById("totalProgressText").textContent = "所有文件上传完成";
            
            // 3秒后刷新页面（可选，根据需求决定是否保留）
            setTimeout(() => window.location.reload(), 3000);
        }

        // 初始化拖拽功能
        document.addEventListener('DOMContentLoaded', initDragAndDrop);
    </script>
</body>
</html>
